/**
 * @class 变换器（3D）
 * @author DuskyHuang 圣子
 * @description 为3D节点提供快捷方便的平移、旋转、缩放和加速变换。
*/

import { Component, _decorator, v3, Vec3, Quat } from "cc";
import { Limitation } from "../misc/limitation";
import { group } from "../../utils";
const { ccclass } = _decorator;
const { None } = Limitation.Type;
function showTranslate(this: Transformer) { return this.useTranslate; }
function showPosLimitation(this: Transformer) { return this.useTranslate && this.positionLimited; }
function showRotate(this: Transformer) { return this.useRotate; }
function showEulerLimitation(this: Transformer) { return this.useRotate && this.eulerLimited; }
function showScale(this: Transformer) { return this.useScale; }
function showScaleLimitation(this: Transformer) { return this.useScale && this.scaleLimited; }

@ccclass("cfw.Transformer")
export class Transformer extends Component {

    @group("Translate", "启用平移变换") public useTranslate: boolean = false;
    @group("Translate", "相对世界变换", showTranslate, "Use World") public worldTranslate: boolean = false;
    @group("Translate", "初始线速度", showTranslate) public initLinearVelocity: Vec3 = v3();
    @group("Translate", "线加速度", showTranslate) public linearAcceleration: Vec3 = v3();
    @group("Translate", "启用位置约束", showTranslate) public positionLimited: boolean = false;
    @group("Translate", "X轴范围限制", showPosLimitation) public xPosLimitation: Limitation = new Limitation(None, 0, 100);
    @group("Translate", "Y轴范围限制", showPosLimitation) public yPosLimitation: Limitation = new Limitation(None, 0, 100);
    @group("Translate", "Z轴范围限制", showPosLimitation) public zPosLimitation: Limitation = new Limitation(None, 0, 100);

    @group("Rotate", "启用旋转变换") public useRotate: boolean = false;
    @group("Rotate", "相对世界变换", showRotate, "Use World") public worldRotate: boolean = false;
    @group("Rotate", "欧拉角速度", showRotate, ['unit', 'deg/s']) public initEulerVelocity: Vec3 = v3();
    @group("Rotate", "欧拉角加速度", showRotate, ['unit', 'deg/s^2']) public eulerAcceleration: Vec3 = v3();
    @group("Rotate", "启用欧拉角约束", showRotate) public eulerLimited: boolean = false;
    @group("Rotate", "X欧拉角范围限制", showEulerLimitation) public xEulerLimitation: Limitation = new Limitation(None, 0, 360);
    @group("Rotate", "Y欧拉角范围限制", showEulerLimitation) public yEulerLimitation: Limitation = new Limitation(None, 0, 360);
    @group("Rotate", "Z欧拉角范围限制", showEulerLimitation) public zEulerLimitation: Limitation = new Limitation(None, 0, 360);

    @group("Scale", "使用缩放") public useScale: boolean = false;
    @group("Scale", "相对世界变换", showScale, "Use World") public worldScale: boolean = false;
    @group("Scale", "缩放速率", showScale) public initScaleVelocity: Vec3 = v3();
    @group("Scale", "缩放加速率", showScale) public scaleAcceleration: Vec3 = v3();
    @group("Scale", "启用缩放约束", showScale) public scaleLimited: boolean = false;
    @group("Scale", "X轴缩放比限制", showScaleLimitation) public xScaleLimitation: Limitation = new Limitation(None, 0, 1);
    @group("Scale", "Y轴缩放比限制", showScaleLimitation) public yScaleLimitation: Limitation = new Limitation(None, 0, 1);
    @group("Scale", "Z轴缩放比限制", showScaleLimitation) public zScaleLimitation: Limitation = new Limitation(None, 0, 1);

    /** 当前瞬时线速度 */   public linearVelocity: Vec3 = v3();
    /** 当前瞬时角速度 */   public eulerVelocity: Vec3 = v3();
    /** 当前瞬时缩放速度 */ public scaleVelocity: Vec3 = v3();

    private _euler: Vec3 = v3();
    private _quat: Quat = new Quat();
    private _last = { position: v3(), euler: v3(), scale: v3() };

    protected onEnable(): void {
        this.linearVelocity.set(this.initLinearVelocity);
        this.eulerVelocity.set(this.initEulerVelocity);
        this.scaleVelocity.set(this.initScaleVelocity);
    }

    protected start(): void {
        const { worldTranslate, worldRotate, worldScale } = this;
        this._last.position.set(worldTranslate ? this.node.worldPosition : this.node.position);
        this._last.euler.set(worldRotate ? this.node.getWorldRotation(this._quat)
            .getEulerAngles(this._euler) as Vec3 : this.node.eulerAngles);
        this._last.scale.set(worldScale ? this.node.worldScale : this.node.scale);
    }

    protected lateUpdate(dt: time.sec): void {
        const { useTranslate, useRotate, useScale } = this;
        if (useTranslate) this._translate(dt);
        if (useRotate) this._rotate(dt);
        if (useScale) this._scale(dt);
    }

    private _translate(dt: time.sec): void {
        const { _last: { position }, worldTranslate: world, linearVelocity: v,
            linearAcceleration: acc, positionLimited } = this;
        const { x: vx, y: vy, z: vz } = Vec3.scaleAndAdd(v, v, acc, dt);
        const [dx, dy, dz] = [vx * dt, vy * dt, vz * dt];
        let { x, y, z } = position;
        if (positionLimited) {
            const [_x, _dx] = this.xPosLimitation.limit(x, dx);
            const [_y, _dy] = this.yPosLimitation.limit(y, dy);
            const [_z, _dz] = this.zPosLimitation.limit(z, dz);
            v.set(_dx / dt, _dy / dt, _dz / dt);
            x = _x; y = _y; z = _z;
        } else {
            x += dx; y += dy; z += dy;
        }
        position.set(x, y, z);
        world ? this.node.setWorldPosition(position) : this.node.setPosition(position);
    }

    private _rotate(dt: time.sec): void {
        const { _last: { euler }, worldRotate: world, eulerVelocity: v, eulerAcceleration, eulerLimited } = this;
        const { x: vx, y: vy, z: vz } = Vec3.scaleAndAdd(v, v, eulerAcceleration, dt);
        const [dx, dy, dz] = [vx * dt, vy * dt, vz * dt];
        let { x, y, z } = euler;
        if (eulerLimited) {
            const [_x, _dx] = this.xEulerLimitation.limit(x, dx);
            const [_y, _dy] = this.yEulerLimitation.limit(y, dy);
            const [_z, _dz] = this.zEulerLimitation.limit(z, dz);
            v.set(_dx / dt, _dy / dt, _dz / dt);
            x = _x; y = _y; z = _z;
        } else {
            x += dx; y += dy; z += dz;
        }
        euler.set(x, y, z);
        world ? this.node.setWorldRotationFromEuler(x, y, z) : this.node.setRotationFromEuler(euler);
    }

    private _scale(dt: time.sec): void {
        const { _last: { scale }, worldScale: world, scaleVelocity: v,
            scaleAcceleration: acc, scaleLimited } = this;
        const { x: vx, y: vy, z: vz } = Vec3.scaleAndAdd(v, v, acc, dt);
        const [dx, dy, dz] = [vx * dt, vy * dt, vz * dt];
        let { x, y, z } = scale;
        if (scaleLimited) {
            const [_x, _dx] = this.xScaleLimitation.limit(x, dx);
            const [_y, _dy] = this.yScaleLimitation.limit(y, dy);
            const [_z, _dz] = this.zScaleLimitation.limit(z, dz);
            this.scaleVelocity.set(_dx / dt, _dy / dt, _dz / dt);
            x = _x; y = _y; z = _z;
        } else {
            x += dx; y += dy; z += dz;
        }
        scale.set(x, y, z);
        world ? this.node.setWorldScale(scale) : this.node.setScale(scale);
    }
}